package com.element.oauth2.server.global.converter;

import com.element.oauth2.constant.SecurityParams;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class OAuthAuthorizationCodeConverter implements AuthenticationConverter {

    @Override
    public Authentication convert(HttpServletRequest request) {
        String grantType = request.getParameter(SecurityParams.GRANT_TYPE);
        if (!AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(grantType)) {
            return null;
        } else {
            Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
            MultiValueMap<String, String> parameters = getParameters(request);
            String code = parameters.getFirst("code");
            if (!StringUtils.hasText(code) || parameters.get("code").size() != 1) {
                throwError();
            }
            String redirectUri = null;
            if (parameters.containsKey(SecurityParams.REDIRECT_URL)) {
                redirectUri = parameters.getFirst(SecurityParams.REDIRECT_URL);
            }
            Map<String, Object> additionalParameters = new HashMap<>();
            parameters.forEach((key, value) -> {
                if (!key.equals(SecurityParams.GRANT_TYPE) &&
                        !key.equals(SecurityParams.CLIENT_ID) &&
                        !key.equals("code")) {
                    additionalParameters.put(key, value.size() == 1 ? value.get(0) : value.toArray(new String[0]));
                }
            });
            return new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, redirectUri, additionalParameters);
        }
    }

    private MultiValueMap<String, String> getParameters(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
        parameterMap.forEach((key, values) -> {
            for (String value : values) {
                parameters.add(key, value);
            }
        });
        return parameters;
    }

    private void throwError() {
        OAuth2Error error = new OAuth2Error("invalid_request", "OAuth 2.0 Parameter: " + "code", null);
        throw new OAuth2AuthenticationException(error);
    }
}